package com.wenbing.miio.jsonrpc;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.wenbing.miio.util.function.Verifier;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.stream.Stream;

/**
 * @author wen bing
 * @projectName: miio
 * @package: com.wenbing.miio.mi.device.simulator
 * @className: RpcInvoker
 * @date 2024/09/23 14:46
 **/
public class JsonRpcInvoker {

    /**
     * 方法校验器
     */
    private Verifier<Method> methodVerifier;

    /**
     * 方法缓存
     */
    private Map<String, Method> methods;

    private boolean overloadSupported = false;

    public JsonRpcInvoker() {
        this.methods = new HashMap<>();
        this.methodVerifier = (method)-> {
            int modifiers = method.getModifiers();
            return Modifier.isPublic(modifiers)
                    && Modifier.isStatic(modifiers)
                    && Modifier.isFinal(modifiers);
        };
    }

    /**
     * 设置是否允许方法覆盖
     * @param overloadSupported
     */
    public void setOverloadSupported(boolean overloadSupported) {
        this.overloadSupported = overloadSupported;
    }

    /**
     * 返回所有方法名的集合
     * @return
     */
    public Set<String> functionNames() {
        return methods.keySet();
    }

    /**
     * 执行JsonRpc方法
     * @param message
     * @return
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     * @throws IllegalAccessException
     */
    public Object invoke(JsonRpcMessage message) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
        String methodName = message.getMethod();
        Method method = methods.get(methodName);
        if (method == null) {
            throw new NoSuchMethodException(methodName);
        }

        JSONArray array = toArray(message.getParams());
        Parameter[] parameters = method.getParameters();
        if (array.size() < parameters.length) {
            throw new IllegalArgumentException();
        }

        Object[] input = toInputParameter(array, parameters);
        return method.invoke(null, input);
    }

    /**
     * 注册方法
     * @param name
     * @param method
     */
    public void register(String name, Method method) {
        if (overload(name, method)) {
            throw new OverloadException(name);
        }
        if (this.methodVerifier.verify(method)) {
            throw new UnsupportedOperationException(method.getName());
        }
        methods.put(name, method);
    }

    /**
     * 通过类和JsonRpcInterface注解注册方法
     * @param clazz
     */
    public void register(Class clazz) {
        JsonRpcInterface rpcInterface = (JsonRpcInterface) clazz.getDeclaredAnnotation(JsonRpcInterface.class);
        String prefix = rpcInterface == null ? "" : rpcInterface.value();
        Method[] methods = clazz.getMethods();
        Stream<Method> methodStream = Arrays.stream(methods)
                .filter(this.methodVerifier::verify);
        if (rpcInterface == null) {
            methodStream = methodStream.filter(this::methodFilter);
        }

        methodStream.forEach((m)-> {
            registerJsonRpcInterface(prefix, m);
        });
    }

    /**
     * 注册 rpc接口方法
     * @param prefix
     * @param method
     */
    private void registerJsonRpcInterface(String prefix, Method method) {
        JsonRpcInterface rpcInterface = method.getAnnotation(JsonRpcInterface.class);
        if (!this.methodVerifier.verify(method) ) {
            return;
        }
        boolean ignore = rpcInterface == null ? false : rpcInterface.ignorePrefix();
        String methodName = rpcInterface.value();
        register( (ignore ? "" : prefix) + methodName, method);
    }

    /**
     * 过滤有注解的方法方法
     * @param method
     * @return
     */
    private boolean methodFilter(Method method) {
        return method.getAnnotation(JsonRpcInterface.class) != null;
    }

    /**
     * 是否为重载方法
     */
    private boolean overload(String name, Method method) {
        Method load = methods.get(name);
        if (load == null || this.overloadSupported) {
            return false;
        }
        return Objects.equals(method.getName(), load.getName());
    }

    /**
     * 将array内的数据映射成入参
     * @param array
     * @param parameters
     * @return
     */
    private Object[] toInputParameter(JSONArray array, Parameter[] parameters) {
        Object[] input = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i ++) {
            Parameter parameter = parameters[i];
            Class clazz = parameter.getType();
            input[i] = map2Object(array.get(i), clazz);
        }
        return input;
    }

    /**
     * 将object转化为JSONArray
     * @param object
     * @return
     */
    private JSONArray toArray(Object object) {
        if (object == null) object = new JSONArray(0);
        if (object instanceof JSONArray array) {
            return array;
        }
        return new JSONArray(Arrays.asList(object));
    }

    /**
     * 将o转化成指定类型
     * @param o
     * @param targetClass
     * @return
     */
    private Object map2Object(Object o, Class targetClass) {
        String c = JSONObject.toJSONString(o);
        return JSONObject.parseObject(c, targetClass);
    }
}
